#include "WindowVisibilityHelper.h"

///Copyright (c) 2009-2010 T. Klepzig 

set<WindowVisibilityHelper*> WindowVisibilityHelper::instances;
HWINEVENTHOOK WindowVisibilityHelper::hWinEventHook;
UINT WindowVisibilityHelper::WM_TASKBARCREATED = 0;

WindowVisibilityHelper::WindowVisibilityHelper(HINSTANCE hInstance, bool removeFromTaskbar)
{
	tag = 0;

	if (!WM_TASKBARCREATED)
		WM_TASKBARCREATED = RegisterWindowMessage(TEXT("TaskbarCreated"));

	this->removeFromTaskbar = removeFromTaskbar;

	if (instances.empty())
		hWinEventHook=SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_LOCATIONCHANGE,0,WinEventProc,0,0,0);


	wmCallback = new WMCallback(hInstance, this);
	wmCallback->OnTaggedWmCallback(WndProc);

	instances.insert(this);
}


WindowVisibilityHelper::~WindowVisibilityHelper()
{
	Recover();

	instances.erase(this);

	delete wmCallback;
	wmCallback = 0;

	if (instances.empty())
		UnhookWinEvent(hWinEventHook);
}

void WindowVisibilityHelper::Hide(HWND hWnd)
{
	if (!hWnd)
		return;

	if (!IsWindow(hWnd))
		return;

	if (removeFromTaskbar)
	{
		//remove from taskbar
		DeleteTaskbarTab(hWnd);

		//remove from Flip3D
		RemoveFromFlip3D(hWnd);
	}

	if (IsHidden(hWnd))
		return;	

	//move
	POINT originalWindowLocation = SetHiddenPosition(hWnd);

	//disable
	EnableWindow(hWnd, FALSE);

	HANDLE handle = (HANDLE)MAKELPARAM(originalWindowLocation.x, originalWindowLocation.y);
	
	//The return value of 0 would be no prop available and at location 0,0.
	//Therefore "define" location 0,0 as -1.
	if (!handle)
		handle = (HANDLE)-1;

	SetProp(hWnd, TEXT("Cassiopeia_HiddenState"), handle);	
}

void WindowVisibilityHelper::Show(HWND hWnd)
{
	if (!hWnd)
		return;

	if (!IsWindow(hWnd))
		return;

	if (removeFromTaskbar)
	{
		//add to Flip3D
		AddToFlip3D(hWnd);

		//add to taskbar
		AddTaskbarTab(hWnd);
	}

	if (!IsHidden(hWnd))
		return;
	

	//enable
	EnableWindow(hWnd, TRUE);

	//move
	SetNormalPosition(hWnd);

	RemoveProp(hWnd, TEXT("Cassiopeia_HiddenState"));
}

void WindowVisibilityHelper::Recover()
{
	EnumWindows(EnumWindowsProc_Recover, (LPARAM)this);
}

bool WindowVisibilityHelper::IsHidden(HWND hWnd)
{
	return GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
}

void WindowVisibilityHelper::SetTag(LPVOID tag)
{
	this->tag = tag;
}

LPVOID WindowVisibilityHelper::GetTag()
{
	return tag;
}

void WindowVisibilityHelper::RemoveFromFlip3D(HWND hWnd)
{
	DWMFLIP3DWINDOWPOLICY flip3DPolicy = DWMFLIP3D_EXCLUDEBELOW;
	DwmSetWindowAttribute(hWnd, DWMWA_FLIP3D_POLICY, &flip3DPolicy, sizeof(DWMWA_FLIP3D_POLICY));
}

void WindowVisibilityHelper::AddToFlip3D(HWND hWnd)
{
	DWMFLIP3DWINDOWPOLICY flip3DPolicy = DWMFLIP3D_DEFAULT;
	DwmSetWindowAttribute(hWnd, DWMWA_FLIP3D_POLICY, &flip3DPolicy, sizeof(DWMWA_FLIP3D_POLICY));
}

POINT WindowVisibilityHelper::SetHiddenPosition(HWND hWnd)
{
	POINT windowLocation = {0};	

	if (!IsIconic(hWnd))
	{	
		if (!IsHidden(hWnd))
		{
			RECT rect = {0};
			GetWindowRect(hWnd, &rect);
			windowLocation.x = rect.left;
			windowLocation.y = rect.top;

			SetWindowPos(hWnd, 0, windowLocation.x + HiddenPositionValue, windowLocation.y + HiddenPositionValue, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
		}
		else
		{
			HANDLE handle = GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
			if (handle)
			{
				if (handle == (HANDLE)-1)
					handle = 0;

				windowLocation.x = GET_X_LPARAM(handle);
				windowLocation.y = GET_Y_LPARAM(handle);

				SetWindowPos(hWnd, 0, windowLocation.x + HiddenPositionValue, windowLocation.y + HiddenPositionValue, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
			}
		}
	}
	
	#pragma region old
	/*POINT windowLocation = {0};	

	if (!IsIconic(hWnd))
	{	
		if (!IsHidden(hWnd))
		{
			RECT rect = {0};
			GetWindowRect(hWnd, &rect);
			windowLocation.x = rect.left;
			windowLocation.y = rect.top;

			SetWindowPos(hWnd, 0, windowLocation.x + HiddenPositionValue, windowLocation.y + HiddenPositionValue, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE);
		}
		else
		{
			HANDLE handle = GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
			if (handle)
			{
				if (handle == (HANDLE)-1)
					handle = 0;

				windowLocation.x = GET_X_LPARAM(handle);
				windowLocation.y = GET_Y_LPARAM(handle);
				
				SetWindowPos(hWnd, 0, windowLocation.x + HiddenPositionValue, windowLocation.y + HiddenPositionValue, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE);
			}
		}
	}
	else
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof(WINDOWPLACEMENT);
		GetWindowPlacement(hWnd, &wp);

		if (!IsHidden(hWnd))
		{
			windowLocation.x = wp.rcNormalPosition.left;
			windowLocation.y = wp.rcNormalPosition.top;

			wp.rcNormalPosition.right = windowLocation.x + RECTWIDTH(wp.rcNormalPosition);
			wp.rcNormalPosition.bottom = windowLocation.y + RECTHEIGHT(wp.rcNormalPosition);
			wp.rcNormalPosition.left = windowLocation.x + HiddenPositionValue;
			wp.rcNormalPosition.top = windowLocation.y + HiddenPositionValue;
			
			SetWindowPlacement(hWnd, &wp);
		}
		else
		{
			HANDLE handle = GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
			if (handle)
			{
				if (handle == (HANDLE)-1)
					handle = 0;

				windowLocation.x = GET_X_LPARAM(handle);
				windowLocation.y = GET_Y_LPARAM(handle);

				wp.rcNormalPosition.right = windowLocation.x + RECTWIDTH(wp.rcNormalPosition);
				wp.rcNormalPosition.bottom = windowLocation.y + RECTHEIGHT(wp.rcNormalPosition);
				wp.rcNormalPosition.left = windowLocation.x + HiddenPositionValue;
				wp.rcNormalPosition.top = windowLocation.y + HiddenPositionValue;
				
				SetWindowPlacement(hWnd, &wp);
			}
		}
	}*/

	#pragma endregion

	return windowLocation;
}

void WindowVisibilityHelper::SetNormalPosition(HWND hWnd)
{	
	if (!IsIconic(hWnd))
	{
		HANDLE handle = GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
		if (handle)
		{
			if (handle == (HANDLE)-1)
				handle = 0;

			POINT location = {GET_X_LPARAM(handle), GET_Y_LPARAM(handle)};
			SetWindowPos(hWnd, 0, location.x, location.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
		}
	}

	#pragma region old
	/*HANDLE handle = GetProp(hWnd, TEXT("Cassiopeia_HiddenState"));
	if (handle)
	{
		if (handle == (HANDLE)-1)
			handle = 0;

		POINT location = {GET_X_LPARAM(handle), GET_Y_LPARAM(handle)};

		if (!IsIconic(hWnd))
		{
			SetWindowPos(hWnd, 0, location.x, location.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOACTIVATE);
		}
		else
		{
			WINDOWPLACEMENT wp;
			wp.length = sizeof(WINDOWPLACEMENT);
			GetWindowPlacement(hWnd, &wp);
			wp.rcNormalPosition.left = location.x;
			wp.rcNormalPosition.top = location.y;
			wp.rcNormalPosition.right = location.x + RECTWIDTH(wp.rcNormalPosition);
			wp.rcNormalPosition.bottom = location.y + RECTHEIGHT(wp.rcNormalPosition);
			SetWindowPlacement(hWnd, &wp);
		}
	}*/
	#pragma endregion
}

void WindowVisibilityHelper::DeleteTaskbarTab(HWND hWnd)
{
	HRESULT hr = CoInitialize(0);

	if (SUCCEEDED(hr))
	{
		ITaskbarList4 *taskbarList;

		hr = CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbarList));

		if (SUCCEEDED(hr))
		{
			taskbarList->HrInit();
			taskbarList->DeleteTab(hWnd);
			taskbarList->Release();
		}
	}

	CoUninitialize();
}

void WindowVisibilityHelper::AddTaskbarTab(HWND hWnd)
{
	HRESULT hr = CoInitialize(0);

	if (SUCCEEDED(hr))
	{
		ITaskbarList4 *taskbarList;

		hr = CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbarList));

		if (SUCCEEDED(hr))
		{
			taskbarList->HrInit();
			taskbarList->AddTab(hWnd);
			taskbarList->Release();
		}
	}

	CoUninitialize();
}


VOID CALLBACK WindowVisibilityHelper::WinEventProc(HWINEVENTHOOK hWinEventHook,DWORD dwEvent,HWND hWnd,LONG idObject,LONG idChild,DWORD idEventThread,DWORD dwmsEventTime)
{
	//replaced by WM_TASKBARCREATED
	//if ((dwEvent == EVENT_OBJECT_SHOW) && idObject == OBJID_WINDOW)
	//{
		//TCHAR className[255]={0};
		//GetClassName(hWnd,className,sizeof(className));
		//if (!_tcscmp(className, TEXT("Shell_TrayWnd")))
		//{
		//	//explorer was new created, so remove the buttons again
		//	set<WindowVisibilityHelper*>::iterator iter;
		//	for (iter=instances.begin();iter!=instances.end();iter++)
		//	{		
		//		map<HWND, RECT>::iterator iter2;
		//		for (iter2=(*iter)->hiddenHWnds.begin();iter2!=(*iter)->hiddenHWnds.end();iter2++)
		//		{
		//			(*iter)->DeleteTaskbarTab(iter2->first);
		//		}
		//	}
		//}
	//}

	if (dwEvent == EVENT_OBJECT_DESTROY && idObject == OBJID_WINDOW)
	{
		set<WindowVisibilityHelper*>::iterator iter;
		for (iter=instances.begin();iter!=instances.end();iter++)
		{		
			if ((*iter)->IsHidden(hWnd))
			{
				RemoveProp(hWnd, TEXT("Cassiopeia_HiddenState"));
			}
		}	
	}
	else if (dwEvent == EVENT_OBJECT_LOCATIONCHANGE && idObject == OBJID_WINDOW)
	{
		set<WindowVisibilityHelper*>::iterator iter;
		for (iter=instances.begin();iter!=instances.end();iter++)
		{		
			if ((*iter)->IsHidden(hWnd))
				(*iter)->SetHiddenPosition(hWnd);
		}
	}
}

LRESULT CALLBACK WindowVisibilityHelper::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LPVOID tag)
{
	if (uMsg == WM_TASKBARCREATED)
	{
		//taskbar was recreated, so remove the buttons again
		EnumWindows(EnumWindowsProc_RemoveTaskbarButtonAgain, (LPARAM)tag);
	}

	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL CALLBACK WindowVisibilityHelper::EnumWindowsProc_RemoveTaskbarButtonAgain(HWND hWnd, LPARAM lParam)
{
	WindowVisibilityHelper* instance = (WindowVisibilityHelper*)lParam;
	if (instance->IsHidden(hWnd) && instance->removeFromTaskbar)
		instance->DeleteTaskbarTab(hWnd);
	return TRUE;
}

BOOL CALLBACK WindowVisibilityHelper::EnumWindowsProc_Recover(HWND hWnd, LPARAM lParam)
{
	WindowVisibilityHelper* instance = (WindowVisibilityHelper*)lParam;
	instance->Show(hWnd);
	return TRUE;
}
